
package commons;

import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import main.MainTest;


public class ThreadPool {
	
	private int nbThread;
	private ComputeThread[] tabThread;
	private final Semaphore sema;

	public ThreadPool(int nbThread) {
		this.nbThread = nbThread;
		tabThread = new ComputeThread[nbThread];
		sema = new Semaphore(nbThread);

	}
	
	public void start() {
		try {
			sema.acquire(nbThread);
		} catch (InterruptedException ex) {}
				
		for (int i=0; i <nbThread; i++) {
			tabThread[i] = new ComputeThread("Thread n°" + i);
			tabThread[i].start();
		}
		
				// Attente de la fin des jobs
		try {
			sema.acquire(nbThread);
			sema.release(nbThread);
		} catch (InterruptedException ex) {}
		
//		// Il faut etre certain que tous les thread ont bien démarré et ont bien locké leur lock
//		//  avant d'appeler giveJob !
//		while (!haveAllThreadStarted()) {
//			try {	
//				Thread.currentThread().sleep(10);
//			} catch (InterruptedException ex) {}
//		}
	}
	private boolean haveAllThreadStarted() {
		for (int i=0; i<nbThread; i++)
			if (!tabThread[i].bIsStarted)
				return false;
		return true;
	}

	
	// Bloque le thread appelant tant que les job ne sont pas tous terminés
	public void giveJobs(Job[] tabJob) {
		assert(tabJob.length == nbThread);
		
		for (int i=0; i <nbThread; i++)
			tabThread[i].giveJob(tabJob[i]);
		
		// Attente de la fin des jobs
		try {
			sema.acquire(nbThread);
			sema.release(nbThread);
		} catch (InterruptedException ex) {}
	}
	
	// Bloque le thread appelant tant que les thread ne sont pas finis
	public void release(){
		
		for (int i=0; i <nbThread; i++)
			tabThread[i].giveJob(null);
		
		// Attente de la fin des jobs
		try {
			for (int i=0; i <nbThread; i++)
				tabThread[i].join();
		} catch (InterruptedException ex) {}
	}
	
	synchronized static void log(String s) {
		System.out.println(s);
	}
		
	// Appelé par un thread qui debute le job qu'on lui a donné
	private void threadHasStartedJob() {
		synchronized (sema) {
			try {
				sema.acquire();
			} catch (InterruptedException ex) {}
		}
	}
	
	// Appelé par un thread qui a fini le job qu'on lui a donné
	void threadHasFinishedJob() {
		synchronized (sema) {
			sema.release();
		}
	}
	
	
	
	private class ComputeThread extends Thread {
		String nom;
		private final Object lock = new Object();
		public Job job;

		public ComputeThread(String nom) {
			super();
			this.nom = nom;
		}
		
		
		private boolean bIsComputing=false;
		private boolean bIsStarted=false;
		
		public void giveJob(Job job) {
			if (!bIsStarted)
				System.out.println("PAS STARTED !!");
			if (bIsComputing)
				System.out.println("COMPUTING !!");
			
			// On prend un jeton via le thread principal.
			// Sinon, si on demande au thread se debloquant de le faire, le thread principal puorrait déjà
			//  être au niveau de waitEndJobs() avant meme que le thread n'ai tenté de prendre un jeton ! il serait alors bloqué
			threadHasStartedJob();
			
			this.job = job;
			synchronized (lock){
				// on debloque le thread
                lock.notifyAll();
            }
		}
		
		@Override
		public void run() {
			bIsComputing = false;
//			log( nom + " : " + "STARTED");
			while(true) {
				
				// Par défaut, on est bloqué, en attente d'un Job
				synchronized (lock) {
					
					threadHasFinishedJob();
					bIsComputing = false;
					
					try {
						bIsStarted = true;
						lock.wait();
					} catch (InterruptedException e) {
						return;
					}
				}
				
				// on a un job !
				bIsComputing = true;
				if (job==null) {
//					log(nom + " : " + "KILL");
					bIsComputing = false;
					threadHasFinishedJob();
					return;
				}
				
//				log(nom + " : " + "DEBUT job");
				job.executeJob();
//				log(nom + " : " + "FIN job");
				
//				threadHasFinishedJob();
//				bIsComputing = false;
			}
		}

	}
}
